﻿using BSF.BaseService.NScript.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;

namespace BSF.BaseService.NScript.Compiler
{
    public class AppDomainCompiler : BaseCompiler
    {
        private static CompilerCache cache = new CompilerCache();
        public override Core.CompilerResult DoCompiler(CompilerInfo compilerInfo)
        {
            compilerInfo.EnumCompilerMode = EnumCompilerMode.AppDomian;
            return cache.GetOrAdd(compilerInfo.GetHashCode(), () => {
                AppDomainSetup setup = new AppDomainSetup();
                setup.ShadowCopyFiles = "true";
                setup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;
                AppDomain domain = null;
                try
                {
                    domain = AppDomain.CreateDomain("Nscript_" + Guid.NewGuid(), null, setup);
                    if (domain == null)
                        throw new NScriptException("应用程序域为空");
                    AppDomainAssemblyLoader obj = (AppDomainAssemblyLoader)domain.CreateInstanceFromAndUnwrap(
                        this.GetType().Assembly.Location, typeof(AppDomainAssemblyLoader).FullName);
                    if (obj == null)
                        throw new NScriptException("应用程序域加载器为空");
                    var result = obj.LoadAssembly(compilerInfo);
                    if (result.AppDomainCallStatus == AppDomainCallStatus.Error)
                        throw new NScriptException(result.Message);

                    return new CompilerResult()
                    {
                        EnumCompilerMode = Core.EnumCompilerMode.AppDomian,
                        AppDomianInfo = new AppDomianInfo() { AppDomain = domain, AppDomainAssemblyLoader = obj },
                        Assembly = null
                    };
                }
                catch (Exception exp)
                {
                    if (domain != null)
                    {
                        try
                        {
                            AppDomain.Unload(domain);
                        }
                        catch { }
                    }
                    throw exp;
                }
            });
            
        }
    }

        
    public class AppDomainAssemblyLoader : MarshalByRefObject  
    {  
        private Assembly assembly;

        public AppDomainCallResult<MarshalByRefObject> LoadAssembly(CompilerInfo compilerInfo)  
        {
            try
            {
                compilerInfo.EnumCompilerMode = EnumCompilerMode.AppDomian;
                assembly = new DynamicCompilerProvider().Compiler(compilerInfo);
                AssemblyHelper.RegisterAssemblyFind();
                return new AppDomainCallResult<MarshalByRefObject>() { AppDomainCallStatus = AppDomainCallStatus.Success, Data = null, Message = null };
            }
            catch (Exception exp)
            {
                return new AppDomainCallResult<MarshalByRefObject>() { AppDomainCallStatus = AppDomainCallStatus.Error, Data = null, Message = exp.Message };
            }
        }

        public AppDomainCallResult<T> Call<T>(string type, string method, object[] param)
        {
            try
            {
                object obj = assembly.CreateInstance(type);
                if (obj == null)
                    throw new NScriptException("反射对象实例为空");
                MethodInfo objMI = obj.GetType().GetMethod(method);
                if (objMI == null)
                    throw new NScriptException("反射对象方法为空");
                try
                {
                    var data = objMI.Invoke(obj, param);
                    return new AppDomainCallResult<T>()
                    {
                        AppDomainCallStatus = AppDomainCallStatus.Success,
                        Data = (T)data,
                        Message = null
                    };
                }
                catch (Exception exp)
                {
                    throw new NScriptException("调用方法出错:" + exp.Message);
                }
            }
            catch (Exception exp)
            {
                return new AppDomainCallResult<T>() { 
                    AppDomainCallStatus = AppDomainCallStatus.Error, 
                    Data = default(T),
                    Message = exp.Message};
            }
        }
    }
    [Serializable]
    public class AppDomainCallResult<T> 
    {
        public string Message{get;set;}

        public T Data { get; set; }

        public AppDomainCallStatus AppDomainCallStatus { get; set; }
    }

    public enum AppDomainCallStatus
    {
        Success,
        Error,
    }
}
